/*
 * Copyright 2002-2018 the original author or authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      https://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.springframework.web.servlet.mvc.condition;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Iterator;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;

import javax.servlet.http.HttpServletRequest;

import org.springframework.http.InvalidMediaTypeException;
import org.springframework.http.MediaType;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.cors.CorsUtils;
import org.springframework.web.servlet.mvc.condition.HeadersRequestCondition.HeaderExpression;

/**消息内容类型属性字面意思是消费类型，对于http请求来说，消费类型对应于请求中的Content-Type，即请求类型，用于表示是否可以对该Content-Type的请求进行处理
 * <br/>A logical disjunction (' || ') request condition to match a request's
 * 'Content-Type' header to a list of media type expressions. Two kinds of
 * media type expressions are supported, which are described in
 * {@link RequestMapping#consumes()} and {@link RequestMapping#headers()}
 * where the header name is 'Content-Type'. Regardless of which syntax is
 * used, the semantics are the same.
 *
 * @author Arjen Poutsma
 * @author Rossen Stoyanchev
 * @since 3.1
 */
public final class ConsumesRequestCondition extends AbstractRequestCondition<ConsumesRequestCondition> {

	private final static ConsumesRequestCondition PRE_FLIGHT_MATCH = new ConsumesRequestCondition();

	/**消费类型表达式列表，因为需要排序，所以只能使用列表类型
	 */
	private final List<ConsumeMediaTypeExpression> expressions;


	/**构造请求类型条件，传入参数为注解中consumers属性的数组值
	 * <br/>Creates a new instance from 0 or more "consumes" expressions.
	 * @param consumes expressions with the syntax described in
	 * {@link RequestMapping#consumes()}; if 0 expressions are provided,
	 * the condition will match to every request
	 */
	public ConsumesRequestCondition(String... consumes) {
		this(consumes, null);
	}

	/**构造请求类型条件，传入参数为注解中consumers属性的数组值与headers属性的数组值，headers参数用于把其中的Content-Type条件解析为消费类型表达式
	 * <br/>Creates a new instance with "consumes" and "header" expressions.
	 * "Header" expressions where the header name is not 'Content-Type' or have
	 * no header value defined are ignored. If 0 expressions are provided in
	 * total, the condition will match to every request
	 * @param consumes as described in {@link RequestMapping#consumes()}
	 * @param headers as described in {@link RequestMapping#headers()}
	 */
	public ConsumesRequestCondition(String[] consumes, String[] headers) {
		this(parseExpressions(consumes, headers));
	}

	/**最后调用的构造方法，传入解析过的消费类型表达式
	 * <br/>Private constructor accepting parsed media type expressions.
	 */
	private ConsumesRequestCondition(Collection<ConsumeMediaTypeExpression> expressions) {
		// 把传入的参数构造为list结果
		this.expressions = new ArrayList<ConsumeMediaTypeExpression>(expressions);
		// 对表达式列表做一个排序，具体排序逻辑在表达式中
		Collections.sort(this.expressions);
	}

	/**解析消费类型表达式
	 * @param consumes
	 * @param headers
	 * @return
	 */
	private static Set<ConsumeMediaTypeExpression> parseExpressions(String[] consumes, String[] headers) {
		// 结果使用set，保证条件不重复
		Set<ConsumeMediaTypeExpression> result = new LinkedHashSet<ConsumeMediaTypeExpression>();
		if (headers != null) {
			// 遍历@RequestMapping中的请求头属性
			for (String header : headers) {
				// 解析为请求头表达式
				HeaderExpression expr = new HeaderExpression(header);
				// 如果是Content-Type类型的表达式，且value不为空
				if ("Content-Type".equalsIgnoreCase(expr.name)) {
					// 则把该请求头表达式中的值解析为MediaType列表
					for (MediaType mediaType : MediaType.parseMediaTypes(expr.value)) {
						// 依次把列表中媒体类型构造为消费类型表达式
						result.add(new ConsumeMediaTypeExpression(mediaType, expr.isNegated));
					}
				}
			}
		}
		// consumers属性直接遍历并解析为消费类型表达式即可
		if (consumes != null) {
			for (String consume : consumes) {
				result.add(new ConsumeMediaTypeExpression(consume));
			}
		}
		return result;
	}


	/**
	 * Return the contained MediaType expressions.
	 */
	public Set<MediaTypeExpression> getExpressions() {
		return new LinkedHashSet<MediaTypeExpression>(this.expressions);
	}

	/**
	 * Returns the media types for this condition excluding negated expressions.
	 */
	public Set<MediaType> getConsumableMediaTypes() {
		Set<MediaType> result = new LinkedHashSet<MediaType>();
		for (ConsumeMediaTypeExpression expression : this.expressions) {
			if (!expression.isNegated()) {
				result.add(expression.getMediaType());
			}
		}
		return result;
	}

	/**
	 * Whether the condition has any media type expressions.
	 */
	@Override
	public boolean isEmpty() {
		return this.expressions.isEmpty();
	}

	@Override
	protected Collection<ConsumeMediaTypeExpression> getContent() {
		return this.expressions;
	}

	@Override
	protected String getToStringInfix() {
		return " || ";
	}

	/**
	 * Returns the "other" instance if it has any expressions; returns "this"
	 * instance otherwise. Practically that means a method-level "consumes"
	 * overrides a type-level "consumes" condition.
	 */
	@Override
	public ConsumesRequestCondition combine(ConsumesRequestCondition other) {
		// 方法上不为空就用方法上的，否则，使用类型上的
		return !other.expressions.isEmpty() ? other : this;
	}

	/**
	 * Checks if any of the contained media type expressions match the given
	 * request 'Content-Type' header and returns an instance that is guaranteed
	 * to contain matching expressions only. The match is performed via
	 * {@link MediaType#includes(MediaType)}.
	 * @param request the current request
	 * @return the same instance if the condition contains no expressions;
	 * or a new condition with matching expressions only;
	 * or {@code null} if no expressions match
	 */
	@Override
	public ConsumesRequestCondition getMatchingCondition(HttpServletRequest request) {
		// 如果是预检请求，返回预检匹配结果，视为匹配，但匹配结果中匹配内容为空
		if (CorsUtils.isPreFlightRequest(request)) {
			return PRE_FLIGHT_MATCH;
		}
		// 如果consumers条件为空，则返回自身实例，作为匹配的结果，以此来实现注解中未提供属性时所有请求都视为与此条件匹配的逻辑。返回自身实例时，因为内容为空，所以排序时结果排在后面
		if (isEmpty()) {
			return this;
		}

		// 解析请求的内容类型
		MediaType contentType;
		try {
			contentType = (StringUtils.hasLength(request.getContentType()) ?
					MediaType.parseMediaType(request.getContentType()) :
					MediaType.APPLICATION_OCTET_STREAM);
		}
		catch (InvalidMediaTypeException ex) {
			return null;
		}

		// 把表达式列表添加到待匹配集合中
		Set<ConsumeMediaTypeExpression> result = new LinkedHashSet<ConsumeMediaTypeExpression>(this.expressions);
		// 移除结果中不匹配的表达式
		for (Iterator<ConsumeMediaTypeExpression> iterator = result.iterator(); iterator.hasNext();) {
			ConsumeMediaTypeExpression expression = iterator.next();
			// 可以看出只要存在一项匹配，即可返回匹配结果，所以多个表达式之间应该是“或”的关系
			if (!expression.match(contentType)) {
				iterator.remove();
			}
		}
		return (!result.isEmpty() ? new ConsumesRequestCondition(result) : null);
	}

	/**
	 * Returns:
	 * <ul>
	 * <li>0 if the two conditions have the same number of expressions
	 * <li>Less than 0 if "this" has more or more specific media type expressions
	 * <li>Greater than 0 if "other" has more or more specific media type expressions
	 * </ul>
	 * <p>It is assumed that both instances have been obtained via
	 * {@link #getMatchingCondition(HttpServletRequest)} and each instance contains
	 * the matching consumable media type expression only or is otherwise empty.
	 */
	@Override
	public int compareTo(ConsumesRequestCondition other, HttpServletRequest request) {
		// 如果匹配结果中匹配的表达式都为空，则相等，这种情况出现在均未提供consumers属性值时
		if (this.expressions.isEmpty() && other.expressions.isEmpty()) {
			return 0;
		}
		// 否则，为空的匹配结果排序在后面，为空时因为未提供consumers值，排序时当然应该排在提供了consumers值的条件之后
		else if (this.expressions.isEmpty()) {
			return 1;
		}
		else if (other.expressions.isEmpty()) {
			return -1;
		}
		else {
			// 其他情况，以匹配的表达式列表中的第一个表达式来判断两者的排序
			// 直接使用第一个表达式来判断，这是在表达式列表已经排序的基础上进行的。在已排序list情况下，保证两个匹配结果的表达式列表均把最特殊的放在最前面，之后只需要比较最特殊的表达式之间哪个更特殊，
			// 即可确定两个匹配结果哪个也属性更高，即可把特殊性高的匹配结果排在前面。例如在一个@RequestMapping中配置了consumers为text/html;1=0.9，另一个为text/html;q=0.8，
			// 请求类型为text/html时，会选择q=0.9的@RequestMapping作为匹配结果
			return this.expressions.get(0).compareTo(other.expressions.get(0));
		}
	}


	/**
	 * Parses and matches a single media type expression to a request's 'Content-Type' header.
	 */
	static class ConsumeMediaTypeExpression extends AbstractMediaTypeExpression {

		ConsumeMediaTypeExpression(String expression) {
			super(expression);
		}

		ConsumeMediaTypeExpression(MediaType mediaType, boolean negated) {
			super(mediaType, negated);
		}

		public final boolean match(MediaType contentType) {
			// 判断表达式中媒体类型是否包含请求的Content-Type，如果包含，就表示该表达式的媒体类型可以处理Content-Type类型的请求，如text/*是包含text/html的
			boolean match = getMediaType().includes(contentType);
			return (!isNegated() ? match : !match);
		}
	}

}
